/*
  RCSwitch - Arduino libary for remote control outlet switches
  Copyright (c) 2011 Suat �zg�r.  All right reserved.
  
  Contributors:
  - Andre Koehler / info(at)tomate-online(dot)de
  - Gordeev Andrey Vladimirovich / gordeev(at)openpyro(dot)com
  - Skineffect / http://forum.ardumote.com/viewtopic.php?f=2&t=46
  - Dominik Fischer / dom_fischer(at)web(dot)de
  - Frank Oltmanns / <first name>.<last name>(at)gmail(dot)com
  - Andreas Steinel / A.<lastname>(at)gmail(dot)com
  
  Project home: http://code.google.com/p/rc-switch/

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "RCSwitch.h"


/**
 * Switch a remote switch on (Type D REV)
 *
 * @param sGroup        Code of the switch group (A,B,C,D)
 * @param nDevice       Number of the switch itself (1..3)
 */
void RCSwitch::switchOn(char sGroup, int nDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordD(sGroup, nDevice, true) );
}

/**
 * Switch a remote switch off (Type D REV)
 *
 * @param sGroup        Code of the switch group (A,B,C,D)
 * @param nDevice       Number of the switch itself (1..3)
 */
void RCSwitch::switchOff(char sGroup, int nDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordD(sGroup, nDevice, false) );
}

/**
 * Switch a remote switch on (Type C Intertechno)
 *
 * @param sFamily  Familycode (a..f)
 * @param nGroup   Number of group (1..4)
 * @param nDevice  Number of device (1..4)
 */
void RCSwitch::switchOn(char sFamily, int nGroup, int nDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordC(sFamily, nGroup, nDevice, true) );
}

/**
 * Switch a remote switch off (Type C Intertechno)
 *
 * @param sFamily  Familycode (a..f)
 * @param nGroup   Number of group (1..4)
 * @param nDevice  Number of device (1..4)
 */
void RCSwitch::switchOff(char sFamily, int nGroup, int nDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordC(sFamily, nGroup, nDevice, false) );
}

/**
 * Switch a remote switch on (Type B with two rotary/sliding switches)
 *
 * @param nAddressCode  Number of the switch group (1..4)
 * @param nChannelCode  Number of the switch itself (1..4)
 */
void RCSwitch::switchOn(int nAddressCode, int nChannelCode) {
  this->sendProtocol1_01F_repeat( this->getCodeWordB(nAddressCode, nChannelCode, true) );
}

/**
 * Switch a remote switch off (Type B with two rotary/sliding switches)
 *
 * @param nAddressCode  Number of the switch group (1..4)
 * @param nChannelCode  Number of the switch itself (1..4)
 */
void RCSwitch::switchOff(int nAddressCode, int nChannelCode) {
  this->sendProtocol1_01F_repeat( this->getCodeWordB(nAddressCode, nChannelCode, false) );
}

/**
 * Switch a remote switch on (Type A with 10 pole DIP switches)
 *
 * @param sGroup        Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
 * @param sDevice       Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111")
 */
void RCSwitch::switchOn(char* sGroup, char* sDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordA(sGroup, sDevice, true) );
}

/**
 * Switch a remote switch off (Type A with 10 pole DIP switches)
 *
 * @param sGroup        Code of the switch group (refers to DIP switches 1..5 where "1" = on and "0" = off, if all DIP switches are on it's "11111")
 * @param sDevice       Code of the switch device (refers to DIP switches 6..10 (A..E) where "1" = on and "0" = off, if all DIP switches are on it's "11111")
 */
void RCSwitch::switchOff(char* sGroup, char* sDevice) {
  this->sendProtocol1_01F_repeat( this->getCodeWordA(sGroup, sDevice, false) );
}

/**
 * Returns a char[13], representing the Code Word to be send.
 * A Code Word consists of 9 address bits, 3 data bits and one sync bit but in our case only the first 8 address bits and the last 2 data bits were used.
 * A Code Bit can have 4 different states: "F" (floating), "0" (low), "1" (high), "S" (synchronous bit)
 *
 * +-------------------------------+--------------------------------+-----------------------------------------+-----------------------------------------+----------------------+------------+
 * | 4 bits address (switch group) | 4 bits address (switch number) | 1 bit address (not used, so never mind) | 1 bit address (not used, so never mind) | 2 data bits (on|off) | 1 sync bit |
 * | 1=0FFF 2=F0FF 3=FF0F 4=FFF0   | 1=0FFF 2=F0FF 3=FF0F 4=FFF0    | F                                       | F                                       | on=FF off=F0         | S          |
 * +-------------------------------+--------------------------------+-----------------------------------------+-----------------------------------------+----------------------+------------+
 *
 * @param nAddressCode  Number of the switch group (1..4)
 * @param nChannelCode  Number of the switch itself (1..4)
 * @param bStatus       Wether to switch on (true) or off (false)
 *
 * @return char[13]
 */
char* RCSwitch::getCodeWordB(int nAddressCode, int nChannelCode, boolean bStatus) {
  int nReturnPos = 0;
  static char sReturn[13];

  char* code[5] = { 
    "FFFF", "0FFF", "F0FF", "FF0F", "FFF0"     };
  if (nAddressCode < 1 || nAddressCode > 4 || nChannelCode < 1 || nChannelCode > 4) {
    return '\0';
  }
  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = code[nAddressCode][i];
  }

  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = code[nChannelCode][i];
  }

  sReturn[nReturnPos++] = 'F';
  sReturn[nReturnPos++] = 'F';
  sReturn[nReturnPos++] = 'F';

  if (bStatus) {
    sReturn[nReturnPos++] = 'F';
  } 
  else {
    sReturn[nReturnPos++] = '0';
  }

  sReturn[nReturnPos] = '\0';

  return sReturn;
}

/**
 * Returns a char[13], representing the Code Word to be send.
 *
 * getCodeWordA(char*, char*)
 *
 */
char* RCSwitch::getCodeWordA(char* sGroup, char* sDevice, boolean bOn) {
  static char sDipSwitches[13];
  int i = 0;
  int j = 0;

  for (i=0; i < 5; i++) {
    if (sGroup[i] == '0') {
      sDipSwitches[j++] = 'F';
    } 
    else {
      sDipSwitches[j++] = '0';
    }
  }

  for (i=0; i < 5; i++) {
    if (sDevice[i] == '0') {
      sDipSwitches[j++] = 'F';
    } 
    else {
      sDipSwitches[j++] = '0';
    }
  }

  if ( bOn ) {
    sDipSwitches[j++] = '0';
    sDipSwitches[j++] = 'F';
  } 
  else {
    sDipSwitches[j++] = 'F';
    sDipSwitches[j++] = '0';
  }

  sDipSwitches[j] = '\0';

  return sDipSwitches;
}

/**
 * Like getCodeWord (Type C = Intertechno)
 */
char* RCSwitch::getCodeWordC(char sFamily, int nGroup, int nDevice, boolean bStatus) {
  static char sReturn[13];
  int nReturnPos = 0;

  if ( (byte)sFamily < 97 || (byte)sFamily > 112 || nGroup < 1 || nGroup > 4 || nDevice < 1 || nDevice > 4) {
    return '\0';
  }

  char* sDeviceGroupCode =  dec2binWzerofill(  (nDevice-1) + (nGroup-1)*4, 4  );
  char familycode[16][5] = { 
    "0000", "F000", "0F00", "FF00", "00F0", "F0F0", "0FF0", "FFF0", "000F", "F00F", "0F0F", "FF0F", "00FF", "F0FF", "0FFF", "FFFF"     };
  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = familycode[ (int)sFamily - 97 ][i];
  }
  for (int i = 0; i<4; i++) {
    sReturn[nReturnPos++] = (sDeviceGroupCode[3-i] == '1' ? 'F' : '0');
  }
  sReturn[nReturnPos++] = '0';
  sReturn[nReturnPos++] = 'F';
  sReturn[nReturnPos++] = 'F';
  if (bStatus) {
    sReturn[nReturnPos++] = 'F';
  } 
  else {
    sReturn[nReturnPos++] = '0';
  }
  sReturn[nReturnPos] = '\0';
  return sReturn;
}

/**
 * Decoding for the REV Switch Type
 *
 * Returns a char[13], representing the Tristate to be send.
 * A Code Word consists of 7 address bits and 5 command data bits.
 * A Code Bit can have 3 different states: "F" (floating), "0" (low), "1" (high)
 *
 * +-------------------------------+--------------------------------+-----------------------+
 * | 4 bits address (switch group) | 3 bits address (device number) | 5 bits (command data) |
 * | A=1FFF B=F1FF C=FF1F D=FFF1   | 1=0FFF 2=F0FF 3=FF0F 4=FFF0    | on=00010 off=00001    |
 * +-------------------------------+--------------------------------+-----------------------+
 *
 * Source: http://www.the-intruder.net/funksteckdosen-von-rev-uber-arduino-ansteuern/
 *
 * @param sGroup        Name of the switch group (A..D, resp. a..d) 
 * @param nDevice       Number of the switch itself (1..3)
 * @param bStatus       Wether to switch on (true) or off (false)
 *
 * @return char[13]
 */

char* RCSwitch::getCodeWordD(char sGroup, int nDevice, boolean bStatus){
  static char sReturn[13];
  int nReturnPos = 0;

  // Building 4 bits address
  // (Potential problem if dec2binWcharfill not returning correct string)
  char *sGroupCode;
  switch(sGroup){
  case 'a':
  case 'A':
    sGroupCode = dec2binWcharfill(8, 4, 'F'); 
    break;
  case 'b':
  case 'B':
    sGroupCode = dec2binWcharfill(4, 4, 'F'); 
    break;
  case 'c':
  case 'C':
    sGroupCode = dec2binWcharfill(2, 4, 'F'); 
    break;
  case 'd':
  case 'D':
    sGroupCode = dec2binWcharfill(1, 4, 'F'); 
    break;
  default:
    return '\0';
  }

  for (int i = 0; i<4; i++)
  {
    sReturn[nReturnPos++] = sGroupCode[i];
  }


  // Building 3 bits address
  // (Potential problem if dec2binWcharfill not returning correct string)
  char *sDevice;
  switch(nDevice) {
  case 1:
    sDevice = dec2binWcharfill(4, 3, 'F'); 
    break;
  case 2:
    sDevice = dec2binWcharfill(2, 3, 'F'); 
    break;
  case 3:
    sDevice = dec2binWcharfill(1, 3, 'F'); 
    break;
  default:
    return '\0';
  }

  for (int i = 0; i<3; i++)
    sReturn[nReturnPos++] = sDevice[i];

  // fill up rest with zeros
  for (int i = 0; i<5; i++)
    sReturn[nReturnPos++] = '0';

  // encode on or off
  if (bStatus)
    sReturn[10] = '1';
  else
    sReturn[11] = '1';

  // last position terminate string
  sReturn[12] = '\0';
  return sReturn;

}


void RCSwitch::sendProtocol1_01F_repeat(char* code){
 
 for (int i=0; i<nRepeatTransmit; i++) { 
   sendProtocol1_01F(code);
 }
}




/**
 * Turns a decimal value to its binary representation
 */
static char * dec2binWzerofill(unsigned long long dec, unsigned int bitLength){
  static char bin[65];

  for (unsigned int i = 0; i< bitLength; i++) {
    bin[i] = ((dec >> (bitLength - i - 1)) & 1 > 0) ? '1' : '0';
  }

  bin[bitLength] = '\0';
  return bin;
}

char* RCSwitch::dec2binWcharfill(unsigned long Dec, unsigned int bitLength, char fill){
  static char bin[64];
  unsigned int i=0;

  while (Dec > 0) {
    bin[32+i++] = ((Dec & 1) > 0) ? '1' : fill;
    Dec = Dec >> 1;
  }

  for (unsigned int j = 0; j< bitLength; j++) {
    if (j >= bitLength - i) {
      bin[j] = bin[ 31 + i - (j - (bitLength - i)) ];
    }else {
      bin[j] = fill;
    }
  }
  bin[bitLength] = '\0';
 
  return bin;
}




